package org.moonshot.chatty.parse;

import java.io.PrintStream;

import org.moonshot.chatty.ChattyInternalError;

public class ChattyWriter
{
	private PrintStream mOut;
	private int         tabs;
	
	public ChattyWriter (PrintStream aOut)
	{
		mOut = aOut;
	}
	
	public void setOutput (PrintStream aOut)
	{
		mOut = aOut;
	}
	
	public void print (ChattyAST aAST) throws ChattyInternalError
	{
		if( aAST.type != ASTType.METHOD )
			throw new ChattyInternalError (null,
			        "method print(ChattyAST) can only serialize complete methods");
		
		printMethod (aAST);
	}
	
	public void printMethod (ChattyAST aAST) throws ChattyInternalError
	{
		switch( aAST.get (ASTKey.METHOD_SCOPE, MethodScope.class) )
		{
		case CLASS:
			mOut.print ("class ");
			break;
		case CONSTRUCTOR:
			mOut.print ("new ");
			break;
		}
		
		switch( aAST.get (ASTKey.METHOD_TYPE, MethodType.class) )
		{
		case UNARY:
			mOut.print (aAST.get (ASTKey.METHOD_NAME, String.class));
			break;
		
		case BINARY:
			mOut.print (aAST.get (ASTKey.METHOD_NAME, String.class));
			mOut.print (' ');
			mOut.print (aAST.get (ASTKey.METHOD_ARGS, ChattyAST.class).get (0));
			break;
		
		case KEYWORD:
			final ChattyAST args = aAST.get (ASTKey.METHOD_ARGS, ChattyAST.class);
			final String[] words = aAST.get (ASTKey.METHOD_NAME, String.class).split (":");
			mOut.print (words[0]);
			mOut.print (": ");
			mOut.print (args.get (0));
			int i = 1;
			while( i < words.length && words[i].length () > 0 )
			{
				mOut.print (' ');
				mOut.print (words[i]);
				mOut.print (": ");
				mOut.print (args.get (i));
				i++;
			}
		}
		
		mOut.println ();
		
		final ChattyAST args = aAST.get (ASTKey.METHOD_VARS, ChattyAST.class);
		if( args.size () > 0 )
		{
			mOut.print ("\t| ");
			for( int i = 0; i < args.size (); i++ )
			{
				mOut.print (args.get (i));
				mOut.print (' ');
			}
			mOut.println ("|");
		}
		
		tabs = 1;
		printBody (aAST.get (ASTKey.BODY, ChattyAST.class));
	}
	
	private void printBody (ChattyAST aAST) throws ChattyInternalError
	{
		if( aAST.type != ASTType.SEQUENCE )
			throw new ChattyInternalError (null,
			        "Cannot print a non-sequence node as method or block body");
		
		for( int i = 0; i < aAST.size (); i++ )
			printStatement ((ChattyAST) aAST.get (i), i == aAST.size () - 1, tabs);
	}
	
	public void printStatement (ChattyAST aAST, boolean aLast) throws ChattyInternalError
	{
		printStatement (aAST, aLast, 0);
	}
	
	public void printStatement (ChattyAST aAST, boolean aLast, int aTabs)
	        throws ChattyInternalError
	{
		for( int i = 0; i < aTabs; i++ )
			mOut.print ('\t');
		
		if( aAST.type == ASTType.RETURN )
		{
			mOut.print ("^ ");
			printExpr (aAST.get (ASTKey.VALUE, ChattyAST.class));
		}
		else
			printExpr (aAST);
		
		if( !aLast )
			mOut.print ('.');
		mOut.println ();
	}
	
	public void printExpr (ChattyAST aAST) throws ChattyInternalError
	{
		final ChattyAST value = aAST.get (ASTKey.VALUE, ChattyAST.class);
		
		switch( aAST.type )
		{
		case EVAL:
			printEval (aAST);
			break;
		
		case ASSIGN:
			mOut.print (aAST.get (ASTKey.ASSIGN_TO, String.class));
			mOut.print (" := ");
			printExpr (value);
			break;
		
		case RETURN:
			mOut.print ("^ ");
			printExpr (value);
			break;
		
		case ARRAY_LITERAL:
			mOut.print ("$(");
			for(int i = 0; i < aAST.size (); i++)
			{
				if( i > 0 )
					mOut.print(' ');
				
				ChattyAST e = (ChattyAST) aAST.get (i);
				if( e.type == ASTType.CALL_UNARY || e.type == ASTType.CALL_BINARY || e.type == ASTType.CALL_KEYWORD )
				{
					mOut.print('(');
					printExpr (e);
					mOut.print(')');
				}
				else
					printExpr (e);
			}
			mOut.print (")");
			break;
			
		case BLOCK:
			printBlock (aAST);
			break;
		
		case CALL_UNARY:
			if( value.type == ASTType.CALL_KEYWORD )
			{
				printExpr (value);
				mOut.print (',');
			}
			else if( value.type != ASTType.EVAL && value.type != ASTType.CALL_UNARY )
			{
				mOut.print ('(');
				printExpr (value);
				mOut.print (')');
			}
			else
				printExpr (value);
			
			mOut.print (' ');
			mOut.print (aAST.get (ASTKey.UNARY_NAME, String.class));
			break;
		
		case CALL_BINARY:
			if( value.type == ASTType.CALL_KEYWORD )
			{
				printExpr (value);
				mOut.print (',');
			}
			else if( value.type != ASTType.EVAL && value.type != ASTType.CALL_UNARY
			        && value.type != ASTType.CALL_BINARY )
			{
				mOut.print ('(');
				printExpr (value);
				mOut.print (')');
			}
			else
				printExpr (value);
			
			mOut.print (' ');
			mOut.print (aAST.get (ASTKey.BINARY_NAME, String.class));
			mOut.print (' ');
			printExpr (aAST.get (ASTKey.BINARY_VALUE, ChattyAST.class));
			break;
		
		case CALL_KEYWORD:
			if( value.type == ASTType.CALL_KEYWORD )
			{
				printExpr (value);
				mOut.print (',');
			}
			else
				printExpr (value);
			
			final ChattyAST params = aAST.get (ASTKey.KEYWORD_VALUES, ChattyAST.class);
			final String[] words = aAST.get (ASTKey.KEYWORD_NAME, String.class).split (":");
			for( int i = 0; i < words.length; i++ )
			{
				mOut.print (' ');
				mOut.print (words[i]);
				mOut.print (':');
				
				final ChattyAST param = (ChattyAST) params.get (i);
				if( param.type == ASTType.CALL_KEYWORD )
				{
					mOut.print ('(');
					printExpr (param);
					mOut.print (')');
				}
				else if ( param.type == ASTType.EVAL )
				{
					printExpr (param);
				}
				else
				{
					mOut.print (' ');
					printExpr (param);
				}
			}
		}
	}
	
	private void printEval (ChattyAST value)
	{
		final String text = value.get (ASTKey.EVAL_TEXT, String.class);
		switch( value.get (ASTKey.EVAL_KIND, EvalType.class) )
		{
		case IDENTIFIER:
			mOut.print (text);
			break;
		
		case NUMBER:
			mOut.print (text.charAt (0) == '+' ? text.substring (1) : text);
			break;
		
		case STRING:
			mOut.print ('"');
			for( int i = 0; i < text.length (); i++ )
			{
				char at = text.charAt (i);
				if( at == '\n' || at == '\t' || at == '"' || at == '\\' )
				{
					mOut.print ("\\");
					mOut.print (at);
				}
				else if( at < 32 || at > 126 )
				{
					mOut.print ("\\");
					mOut.print (Integer.toOctalString ((int) at));
				}
				else
					mOut.print (at);
			}
			mOut.print ('"');
		}
	}
	
	public void printBlock (ChattyAST aAST) throws ChattyInternalError
	{
		if( aAST.type != ASTType.BLOCK )
			throw new ChattyInternalError (null, "printBlock() can only print BLOCK nodes");
		
		final ChattyAST args = aAST.get (ASTKey.BLOCK_ARGS, ChattyAST.class);
		
		mOut.print ("[");
		if( args.size () > 0 )
		{
			for( int i = 0; i < args.size (); i++ )
			{
				mOut.print (args.get (i));
				mOut.print (": ");
			}
			mOut.print ("|");
		}
		
		final ChattyAST body = aAST.get (ASTKey.BODY, ChattyAST.class);
		if( body.size () > 1 || args.size () > 0 )
		{
			mOut.println ();
			tabs++;
			printBody (body);
			tabs--;
			
			for( int i = 0; i < tabs; i++ )
				mOut.print ('\t');
		}
		else
		{
			mOut.print (' ');
			printExpr ((ChattyAST) body.get (0));
		}
		
		mOut.print (" ]");
	}
}
